/*
 -- IOTA Crypto Core
 --
 -- 2018 by Thomas Pototschnig <microengineer18@gmail.com>
 -- discord: pmaxuw#8292
 -- https://gitlab.com/iccfpga-rv
 --
 -- Permission is hereby granted, free of charge, to any person obtaining
 -- a copy of this software and associated documentation files (the
 -- "Software"), to deal in the Software without restriction, including
 -- without limitation the rights to use, copy, modify, merge, publish,
 -- distribute, sublicense, and/or sell copies of the Software, and to
 -- permit persons to whom the Software is furnished to do so, subject to
 -- the following conditions:
 --
 -- The above copyright notice and this permission notice shall be
 -- included in all copies or substantial portions of the Software.
 --
 -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 -- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 -- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 -- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 -- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 -- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 -- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWAR
 */

#include <unistd.h>
#include <ArduinoJson.h>

#include "utils.h"

#include "api/API.h"

extern API::apiFlagsStruct apiFlags;

namespace API {

	// {"command":"signBundleHash","slot":0,"bundleHash":"TRYTES","security":2,"keyIndex":0}
	int apiSignBundleHash() {
		if (
			!exists<uint32_t>(json, "slot") ||
			!optional<const char*>(json, "auth") ||
			!exists<const char*>(json, "bundleHash") ||
			!exists<uint32_t>(json, "security") ||
			!exists<uint32_t>(json, "keyIndex")
		) {
			return apiError(SBIError::MISSING_OR_INVALID);
		}

		// validate seed slot
		uint32_t slot = json["slot"];
		if (slot > 7) {
			return apiError(SBIError::INVALID_SLOT);
		}

		const char* auth = json["auth"];
		uint8_t authBytes[48]={0};
		if (apiFlags.authMethod == API::AUTH) {
			if (!validateHex(auth, 96))
				return apiError(SBIError::INVALID_AUTH);

			if (!hexToBytes(auth, authBytes, sizeof(authBytes))) {
				return SBIError::INVALID_AUTH;
			}
		}

		// validate bundleHashFragment
		const char* bundleHash = json["bundleHash"];
		if (!validateTrytes(bundleHash, BUNDLEHASH_LEN, false)) {
			return apiError(SBIError::INVALID_BUNDLE_HASH);
		}

		uint32_t securityLevel = json["security"];
		if (securityLevel < 1 || securityLevel > 3) {
			return apiError(SBIError::INVALID_SECLEVEL);
		}

		// how to validate address-index? :thinking:
		uint32_t keyIndex = json["keyIndex"];

		SignBundleHashResponse* resp = SBI::signBundleHash(slot, securityLevel, keyIndex, bundleHash, authBytes);
		if (resp->isError())
			return apiError(resp->code);

		json.clear();
		JsonArray trytesOut = json.createNestedArray("trytes");

		for (uint32_t i=0;i<securityLevel;i++) {
			trytesOut.add(txs[i].getRawPtr());
		}
		return API_SUCCESS;
	}

	// no special privilege needed
//	{"command":"jsonDataTX","timestamp":1500000000,"data":{"hello":"welt"},"tag":"999999999999999999999999999","address":"AOTJZ9QOYAF9RUMJXOUOZYX9MBMSVS9NKPBSCODCJSOHHKAVBG99MHHAYOXQLHQIHWNKHM9HQXXQAINAC","minWeightMagnitude":14,"trunkTransaction":"OF9OFDVHVHYPIAVNLLSBZUCQWNKRHRYCIJT9LZEFNAQ9ADSSEDQWUDUSUIKEVSLUJGTDLGGGVZZHZ9999","branchTransaction":"OF9OFDVHVHYPIAVNLLSBZUCQWNKRHRYCIJT9LZEFNAQ9ADSSEDQWUDUSUIKEVSLUJGTDLGGGVZZHZ9999"}
	int apiJsonDataTX() {
		if (
			!exists<const char*>(json, "address") ||
			!optional<const char*>(json, "tag") ||
			!exists<JsonObject>(json, "data") ||
			!exists<int64_t>(json, "timestamp")
		) {
			return apiError(SBIError::MISSING_OR_INVALID);
		}


		const char* address = json["address"];
		if (!validateTrytes(address, 81)) {
			return apiError(SBIError::INVALID_ADDRESS);
		}

		// tag is optional and variable in length
		const char* tag = json["tag"];
		if (tag) {
			if (!validateTrytesMaxLen(tag, 27)) {
				return apiError(SBIError::INVALID_TAG);
			}
		}

		int64_t timestamp = json["timestamp"];

		JsonObject dataObj = json["data"];

		txs[2].init();
		uint32_t jsonSize = serializeJson(dataObj, txs[2].getsignatureMessageFragment(), 2186/2);
		if (jsonSize >= 2186/2) {
			return apiError(SBIError::DATA_TOO_LONG);
		}

	//	debugPrintf("%.*s\n", 81, address);
		if (strlen(txs[2].getsignatureMessageFragment()) > 2186/2) {
			return apiError(SBIError::DATA_TOO_LONG);
		}

		// memory for temporary json to trytes encoding
		txs[1].init();
		char* pTryteDst = txs[1].getsignatureMessageFragment();
		char* pCharSrc = txs[2].getsignatureMessageFragment();


		if (!asciiToTrytes(pCharSrc, pTryteDst, 2186))
			return apiError(SBIError::DATA_TOO_LONG);

		txs[0].init();
		txs[0].setType(Transaction::Output);
		txs[0].setAddress((char*)address);
		txs[0].setMessage(txs[1].getsignatureMessageFragment());
		if (tag) {
			txs[0].setTag((char*)tag);
		}
		txs[0].setValue(0);
		txs[0].convertIntValues();
		txs[0].deleteLink();

		PrepareTransfersResponse* resp = SBI::prepareTransfersLC(timestamp, (uint32_t) -1, 2, 1, (const uint8_t*) 0);
		if (resp->isError())
			return apiError(resp->code);

		Transaction* tx = resp->first;
		if (!tx) {
			return apiError(SBIError::ERROR);
		}

		char hash[82]={0};	// with zero terminator


		// if trunk, branch and mwm is given, attachToTangle the TXs
		// reduces round-trip-time of sending transactions back and forth
		if (
			exists<const char*>(json, "trunkTransaction") &&
			exists<const char*>(json, "branchTransaction") &&
			exists<uint32_t>(json, "minWeightMagnitude")
		) {
			const char* trunkTransaction = json["trunkTransaction"];
			if (!validateTrytes(trunkTransaction, 81)) {
				return apiError(SBIError::INVALID_TRUNK);
			}

			const char* branchTransaction = json["branchTransaction"];
			if (!validateTrytes(branchTransaction, 81)) {
				return apiError(SBIError::INVALID_BRANCH);
			}

			uint32_t mwm = json["minWeightMagnitude"];
			if (mwm < 3 || mwm > 18) {
				return apiError(SBIError::INVALID_MWM);
			}

			if (!attachToTangle(tx, trunkTransaction, branchTransaction, mwm, (int64_t) timestamp * 1000ll, hash)) {
				return apiError(SBIError::ERROR);
			}
		}
		json.clear();

		json["hash"] = hash;
		JsonArray trytesOut = json.createNestedArray("trytes");
		trytesOut.add(tx->getRawPtr());	// is zero-terminated, so this is fine
		return API_SUCCESS;
	}

	bool registerAPIDefault() {
		return
				registerAPICall("signBundleHash", &apiSignBundleHash) &&
				registerAPICall("jsonDataTX", &apiJsonDataTX);
	}

}


